package com;

import com.handlers.BeanConstants;
import com.handlers.Constants;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;

public class MiniBeanProcessor {
    public <T> T toBean(ResultSet rs,Class<? extends T> type)throws SQLException{
        //1、实例化Bean对象
        T bean = newInstance(type);
        //2、利用bean的一系列的setter方法给bean赋值
        return populateBean(rs,bean);
    }

    //实例化Bean对象的方法，上面的第一步
    public <T> T newInstance(Class<T> c) throws SQLException{
        T newInstances=null;
        try {
            //调用这个方法实例化，潜台词就是T类型必须有一个默认构造函数，否则是不可能创建出对象来的
            newInstances = c.newInstance();
        } catch (InstantiationException e) {
            //捕获了一个反射相关的异常，把其转换为一个SQLException之后重新抛出，
            // 让上层代码知道发生了什么错误
            throw new SQLException("Cannot create "+c.getName()+"： "+e.getMessage());
        } catch (IllegalAccessException e) {
            throw new SQLException("Cannot create "+c.getName()+"： "+e.getMessage());
        }
        return newInstances;
    }

    private <T> T populateBean(ResultSet rs, T bean) throws SQLException {
        //ResultSet与属性（PropertyDescriptors）之间的映射关系
        //获取属性描述
        PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
        ResultSetMetaData rsmd = rs.getMetaData();

        int[] columnsToProperties = mapColumnsToProperties(rsmd, props);

        return populateBean(rs,bean,props,columnsToProperties);
    }

    //获取bean信息的方法
    public PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException {
        BeanInfo beanInfo=null;
        try {
            beanInfo = Introspector.getBeanInfo(c);
        } catch (IntrospectionException e) {
            throw new SQLException("获取bean信息失败");
        }
        return beanInfo.getPropertyDescriptors();
    }

    //真正进行列跟属性的映射操作
    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor [] props) throws SQLException {
        //获取结果集元数据 也就是要查询的列数
        int clos = rsmd.getColumnCount();
        //实体类中的属性
        int[] columnToProperty = new int[clos + 1];
        //首先往数组中所有填充 -1
        Arrays.fill(columnToProperty, BeanConstants.PROPERTY_NOT_FOUND);

        for (int i = 1; i <= clos; i++) {
            //获取列名--》有多少列就循环多少次
            String columnName = getColumnName(rsmd,i);

            //从实体类的所有属性中，找出同名的bean属性，默认bean属性名=列名
            for (int j = 0; j < props.length; j++) {
                //获取属性描述--》依次循环
                PropertyDescriptor descriptor = props[j];

                if(columnName.equals(descriptor.getName())){
                    columnToProperty[i] = j;
                    break;
                }
                //停止内部循环，不要继续遍历，继续外层循环
            }
        }
        return columnToProperty;
        //如果SQL语句：select sid,sname,ssage from student
        //bean : sid,xxx,yyy,sage,sname
        //最后返回的是一个数组 形成了映射关系 [-1,1,5,4]
    }

    //获取列名
    private String getColumnName(ResultSetMetaData rsmd, int i) throws SQLException {
        String columnName = rsmd.getColumnName(i);
        if(columnName == null || columnName.length() == 0){
            columnName = rsmd.getColumnName(i);
        }
        return columnName;
    }


    private <T> T populateBean(ResultSet rs, T bean, PropertyDescriptor[] props, int[] columnsToProperty) throws SQLException {
        //循环遍历数组
        for (int i = 1; i < columnsToProperty.length; i++) {
            int propPos = columnsToProperty[i];
            //一个一个取得 为-1就中断 然后继续下一个
            if(propPos == BeanConstants.PROPERTY_NOT_FOUND){
                continue;
            }
            //不为-1就进入到下面的操作
            PropertyDescriptor descriptor = props[propPos];
            Object value = rs.getObject(i);

            callSetter(bean,descriptor,value);
        }
        return bean;
    }

    private <T> void callSetter(T bean, PropertyDescriptor prop, Object value) {
        Method writeMethod = prop.getWriteMethod();
        try {
            writeMethod.invoke(bean,value);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
